Passed
Branch riff-file (6071b6)
by Rafael S.
02:28
created

WaveFile.fromBuffer   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFile class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import bitDepthLib from 'bitdepth';
33
import * as imaadpcm from 'imaadpcm';
34
import * as alawmulaw from 'alawmulaw';
35
import {encode, decode} from 'base64-arraybuffer-es6';
36
import RIFFFile from './lib/riff-file';
37
import writeString from './lib/write-string';
38
import dwChannelMask from './lib/dw-channel-mask';
39
import interleave from './lib/interleave';
40
import truncateSamples from './lib/truncate-samples';
41
import {unpackArray, packArrayTo, unpackArrayTo,
42
  unpack, packTo, packStringTo, packString, pack} from 'byte-data';
43
44
/**
45
 * A class to read, write and process wav files.
46
 */
47
export default class WaveFile extends RIFFFile {
48
49
  /**
50
   * @param {?Uint8Array=} wavBuffer A wave file buffer.
51
   * @throws {Error} If no 'RIFF' chunk is found.
52
   * @throws {Error} If no 'fmt ' chunk is found.
53
   * @throws {Error} If no 'data' chunk is found.
54
   */
55
  constructor(wavBuffer=null) {
56
    super();
57
    /**
58
     * Audio formats.
59
     * Formats not listed here should be set to 65534,
60
     * the code for WAVE_FORMAT_EXTENSIBLE
61
     * @enum {number}
62
     * @private
63
     */
64
    this.WAV_AUDIO_FORMATS = {
65
      '4': 17,
66
      '8': 1,
67
      '8a': 6,
68
      '8m': 7,
69
      '16': 1,
70
      '24': 1,
71
      '32': 1,
72
      '32f': 3,
73
      '64': 3
74
    };
75
    /**
76
     * The data of the 'fmt' chunk.
77
     * @type {!Object<string, *>}
78
     */
79
    this.fmt = {
80
      /** @type {string} */
81
      chunkId: '',
82
      /** @type {number} */
83
      chunkSize: 0,
84
      /** @type {number} */
85
      audioFormat: 0,
86
      /** @type {number} */
87
      numChannels: 0,
88
      /** @type {number} */
89
      sampleRate: 0,
90
      /** @type {number} */
91
      byteRate: 0,
92
      /** @type {number} */
93
      blockAlign: 0,
94
      /** @type {number} */
95
      bitsPerSample: 0,
96
      /** @type {number} */
97
      cbSize: 0,
98
      /** @type {number} */
99
      validBitsPerSample: 0,
100
      /** @type {number} */
101
      dwChannelMask: 0,
102
      /**
103
       * 4 32-bit values representing a 128-bit ID
104
       * @type {!Array<number>}
105
       */
106
      subformat: []
107
    };
108
    /**
109
     * The data of the 'fact' chunk.
110
     * @type {!Object<string, *>}
111
     */
112
    this.fact = {
113
      /** @type {string} */
114
      chunkId: '',
115
      /** @type {number} */
116
      chunkSize: 0,
117
      /** @type {number} */
118
      dwSampleLength: 0
119
    };
120
    /**
121
     * The data of the 'cue ' chunk.
122
     * @type {!Object<string, *>}
123
     */
124
    this.cue = {
125
      /** @type {string} */
126
      chunkId: '',
127
      /** @type {number} */
128
      chunkSize: 0,
129
      /** @type {number} */
130
      dwCuePoints: 0,
131
      /** @type {!Array<!Object>} */
132
      points: [],
133
    };
134
    /**
135
     * The data of the 'smpl' chunk.
136
     * @type {!Object<string, *>}
137
     */
138
    this.smpl = {
139
      /** @type {string} */
140
      chunkId: '',
141
      /** @type {number} */
142
      chunkSize: 0,
143
      /** @type {number} */
144
      dwManufacturer: 0,
145
      /** @type {number} */
146
      dwProduct: 0,
147
      /** @type {number} */
148
      dwSamplePeriod: 0,
149
      /** @type {number} */
150
      dwMIDIUnityNote: 0,
151
      /** @type {number} */
152
      dwMIDIPitchFraction: 0,
153
      /** @type {number} */
154
      dwSMPTEFormat: 0,
155
      /** @type {number} */
156
      dwSMPTEOffset: 0,
157
      /** @type {number} */
158
      dwNumSampleLoops: 0,
159
      /** @type {number} */
160
      dwSamplerData: 0,
161
      /** @type {!Array<!Object>} */
162
      loops: []
163
    };
164
    /**
165
     * The data of the 'bext' chunk.
166
     * @type {!Object<string, *>}
167
     */
168
    this.bext = {
169
      /** @type {string} */
170
      chunkId: '',
171
      /** @type {number} */
172
      chunkSize: 0,
173
      /** @type {string} */
174
      description: '', //256
175
      /** @type {string} */
176
      originator: '', //32
177
      /** @type {string} */
178
      originatorReference: '', //32
179
      /** @type {string} */
180
      originationDate: '', //10
181
      /** @type {string} */
182
      originationTime: '', //8
183
      /**
184
       * 2 32-bit values, timeReference high and low
185
       * @type {!Array<number>}
186
       */
187
      timeReference: [0, 0],
188
      /** @type {number} */
189
      version: 0, //WORD
190
      /** @type {string} */
191
      UMID: '', // 64 chars
192
      /** @type {number} */
193
      loudnessValue: 0, //WORD
194
      /** @type {number} */
195
      loudnessRange: 0, //WORD
196
      /** @type {number} */
197
      maxTruePeakLevel: 0, //WORD
198
      /** @type {number} */
199
      maxMomentaryLoudness: 0, //WORD
200
      /** @type {number} */
201
      maxShortTermLoudness: 0, //WORD
202
      /** @type {string} */
203
      reserved: '', //180
204
      /** @type {string} */
205
      codingHistory: '' // string, unlimited
206
    };
207
    /**
208
     * The data of the 'ds64' chunk.
209
     * Used only with RF64 files.
210
     * @type {!Object<string, *>}
211
     */
212
    this.ds64 = {
213
      /** @type {string} */
214
      chunkId: '',
215
      /** @type {number} */
216
      chunkSize: 0,
217
      /** @type {number} */
218
      riffSizeHigh: 0, // DWORD
219
      /** @type {number} */
220
      riffSizeLow: 0, // DWORD
221
      /** @type {number} */
222
      dataSizeHigh: 0, // DWORD
223
      /** @type {number} */
224
      dataSizeLow: 0, // DWORD
225
      /** @type {number} */
226
      originationTime: 0, // DWORD
227
      /** @type {number} */
228
      sampleCountHigh: 0, // DWORD
229
      /** @type {number} */
230
      sampleCountLow: 0 // DWORD
231
      /** @type {number} */
232
      //'tableLength': 0, // DWORD
233
      /** @type {!Array<number>} */
234
      //'table': []
235
    };
236
    /**
237
     * The data of the 'data' chunk.
238
     * @type {!Object<string, *>}
239
     */
240
    this.data = {
241
      /** @type {string} */
242
      chunkId: '',
243
      /** @type {number} */
244
      chunkSize: 0,
245
      /** @type {!Uint8Array} */
246
      samples: new Uint8Array(0)
247
    };
248
    /**
249
     * The data of the 'LIST' chunks.
250
     * Each item in this list look like this:
251
     *  {
252
     *      chunkId: '',
253
     *      chunkSize: 0,
254
     *      format: '',
255
     *      subChunks: []
256
     *   }
257
     * @type {!Array<!Object>}
258
     */
259
    this.LIST = [];
260
    /**
261
     * The data of the 'junk' chunk.
262
     * @type {!Object<string, *>}
263
     */
264
    this.junk = {
265
      /** @type {string} */
266
      chunkId: '',
267
      /** @type {number} */
268
      chunkSize: 0,
269
      /** @type {!Array<number>} */
270
      chunkData: []
271
    };
272
    /**
273
     * The bit depth code according to the samples.
274
     * @type {string}
275
     */
276
    this.bitDepth = '0';
277
    /**
278
     * @type {!Object}
279
     * @private
280
     */
281
    this.dataType = {};
282
    // Load a file from the buffer if one was passed
283
    // when creating the object
284
    if (wavBuffer) {
285
      this.fromBuffer(wavBuffer);
286
    }
287
  }
288
289
  /**
290
   * Return the sample at a given index.
291
   * @param {number} index The sample index.
292
   * @return {number} The sample.
293
   * @throws {Error} If the sample index is off range.
294
   */
295
  getSample(index) {
296
    index = index * (this.dataType.bits / 8);
297
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
298
      throw new Error('Range error');
299
    }
300
    return unpack(
301
      this.data.samples.slice(index, index + this.dataType.bits / 8),
302
      this.dataType);
303
  }
304
305
  /**
306
   * Set the sample at a given index.
307
   * @param {number} index The sample index.
308
   * @param {number} sample The sample.
309
   * @throws {Error} If the sample index is off range.
310
   */
311
  setSample(index, sample) {
312
    index = index * (this.dataType.bits / 8);
313
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
314
      throw new Error('Range error');
315
    }
316
    packTo(sample, this.dataType, this.data.samples, index);
317
  }
318
319
  /**
320
   * Set up the WaveFile object based on the arguments passed.
321
   * @param {number} numChannels The number of channels
322
   *    (Integer numbers: 1 for mono, 2 stereo and so on).
323
   * @param {number} sampleRate The sample rate.
324
   *    Integer numbers like 8000, 44100, 48000, 96000, 192000.
325
   * @param {string} bitDepthCode The audio bit depth code.
326
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
327
   *    or any value between '8' and '32' (like '12').
328
   * @param {!Array<number>|!Array<!Array<number>>|!TypedArray} samples
329
   *    The samples. Must be in the correct range according to the bit depth.
330
   * @param {?Object} options Optional. Used to force the container
331
   *    as RIFX with {'container': 'RIFX'}
332
   * @throws {Error} If any argument does not meet the criteria.
333
   */
334
  fromScratch(numChannels, sampleRate, bitDepthCode, samples, options={}) {
335
    if (!options.container) {
336
      options.container = 'RIFF';
337
    }
338
    this.container = options.container;
339
    this.bitDepth = bitDepthCode;
340
    samples = interleave(samples);
341
    this.updateDataType_();
342
    /** @type {number} */
343
    let numBytes = this.dataType.bits / 8;
344
    this.data.samples = new Uint8Array(samples.length * numBytes);
345
    packArrayTo(samples, this.dataType, this.data.samples);
346
    this.clearHeader_();
347
    this.makeWavHeader(
348
      bitDepthCode, numChannels, sampleRate,
349
      numBytes, this.data.samples.length, options);
350
    this.data.chunkId = 'data';
351
    this.data.chunkSize = this.data.samples.length;
352
    this.validateWavHeader_();
353
  }
354
355
  /**
356
   * Set up the WaveFile object from a byte buffer.
357
   * @param {!Uint8Array} bytes The buffer.
358
   * @param {boolean=} samples True if the samples should be loaded.
359
   * @throws {Error} If container is not RIFF, RIFX or RF64.
360
   * @throws {Error} If no 'fmt ' chunk is found.
361
   * @throws {Error} If no 'data' chunk is found.
362
   */
363
  fromBuffer(bytes, samples=true) {
364
    this.clearHeader_();
365
    this.readWavBuffer(bytes, samples);
366
    this.bitDepthFromFmt_();
367
    this.updateDataType_();
368
  }
369
370
  /**
371
   * Return a byte buffer representig the WaveFile object as a .wav file.
372
   * The return value of this method can be written straight to disk.
373
   * @return {!Uint8Array} A .wav file.
374
   * @throws {Error} If any property of the object appears invalid.
375
   */
376
  toBuffer() {
377
    this.validateWavHeader_();
378
    return this.writeWavBuffer();
379
  }
380
381
  /**
382
   * Use a .wav file encoded as a base64 string to load the WaveFile object.
383
   * @param {string} base64String A .wav file as a base64 string.
384
   * @throws {Error} If any property of the object appears invalid.
385
   */
386
  fromBase64(base64String) {
387
    this.fromBuffer(new Uint8Array(decode(base64String)));
388
  }
389
390
  /**
391
   * Return a base64 string representig the WaveFile object as a .wav file.
392
   * @return {string} A .wav file as a base64 string.
393
   * @throws {Error} If any property of the object appears invalid.
394
   */
395
  toBase64() {
396
    /** @type {!Uint8Array} */
397
    let buffer = this.toBuffer();
398
    return encode(buffer, 0, buffer.length);
399
  }
400
401
  /**
402
   * Return a DataURI string representig the WaveFile object as a .wav file.
403
   * The return of this method can be used to load the audio in browsers.
404
   * @return {string} A .wav file as a DataURI.
405
   * @throws {Error} If any property of the object appears invalid.
406
   */
407
  toDataURI() {
408
    return 'data:audio/wav;base64,' + this.toBase64();
409
  }
410
411
  /**
412
   * Use a .wav file encoded as a DataURI to load the WaveFile object.
413
   * @param {string} dataURI A .wav file as DataURI.
414
   * @throws {Error} If any property of the object appears invalid.
415
   */
416
  fromDataURI(dataURI) {
417
    this.fromBase64(dataURI.replace('data:audio/wav;base64,', ''));
418
  }
419
420
  /**
421
   * Force a file as RIFF.
422
   */
423
  toRIFF() {
424
    this.fromScratch(
425
      this.fmt.numChannels,
426
      this.fmt.sampleRate,
427
      this.bitDepth,
428
      unpackArray(this.data.samples, this.dataType));
429
  }
430
431
  /**
432
   * Force a file as RIFX.
433
   */
434
  toRIFX() {
435
    this.fromScratch(
436
      this.fmt.numChannels,
437
      this.fmt.sampleRate,
438
      this.bitDepth,
439
      unpackArray(this.data.samples, this.dataType),
440
      {container: 'RIFX'});
441
  }
442
443
  /**
444
   * Change the bit depth of the samples.
445
   * @param {string} newBitDepth The new bit depth of the samples.
446
   *    One of '8' ... '32' (integers), '32f' or '64' (floats)
447
   * @param {boolean} changeResolution A boolean indicating if the
448
   *    resolution of samples should be actually changed or not.
449
   * @throws {Error} If the bit depth is not valid.
450
   */
451
  toBitDepth(newBitDepth, changeResolution=true) {
452
    /** @type {string} */
453
    let toBitDepth = newBitDepth;
454
    /** @type {string} */
455
    let thisBitDepth = this.bitDepth;
456
    if (!changeResolution) {
457
      if (newBitDepth != '32f') {
458
        toBitDepth = this.dataType.bits.toString();
459
      }
460
      thisBitDepth = this.dataType.bits;
461
    }
462
    this.assureUncompressed_();
463
    /** @type {number} */
464
    let sampleCount = this.data.samples.length / (this.dataType.bits / 8);
465
    /** @type {!Float64Array} */
466
    let typedSamplesInput = new Float64Array(sampleCount);
467
    /** @type {!Float64Array} */
468
    let typedSamplesOutput = new Float64Array(sampleCount);
469
    unpackArrayTo(this.data.samples, this.dataType, typedSamplesInput);
470
    if (thisBitDepth == "32f" || thisBitDepth == "64") {
471
      truncateSamples(typedSamplesInput);
472
    }
473
    bitDepthLib(
474
      typedSamplesInput, thisBitDepth, toBitDepth, typedSamplesOutput);
475
    this.fromScratch(
476
      this.fmt.numChannels,
477
      this.fmt.sampleRate,
478
      newBitDepth,
479
      typedSamplesOutput,
480
      {container: this.correctContainer_()});
481
  }
482
483
  /**
484
   * Encode a 16-bit wave file as 4-bit IMA ADPCM.
485
   * @throws {Error} If sample rate is not 8000.
486
   * @throws {Error} If number of channels is not 1.
487
   */
488
  toIMAADPCM() {
489
    if (this.fmt.sampleRate !== 8000) {
490
      throw new Error(
491
        'Only 8000 Hz files can be compressed as IMA-ADPCM.');
492
    } else if (this.fmt.numChannels !== 1) {
493
      throw new Error(
494
        'Only mono files can be compressed as IMA-ADPCM.');
495
    } else {
496
      this.assure16Bit_();
497
      /** @type {!Int16Array} */
498
      let output = new Int16Array(this.data.samples.length / 2);
499
      unpackArrayTo(this.data.samples, this.dataType, output);
500
      this.fromScratch(
501
        this.fmt.numChannels,
502
        this.fmt.sampleRate,
503
        '4',
504
        imaadpcm.encode(output),
505
        {container: this.correctContainer_()});
506
    }
507
  }
508
509
  /**
510
   * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
511
   * @param {string} bitDepthCode The new bit depth of the samples.
512
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
513
   *    Optional. Default is 16.
514
   */
515
  fromIMAADPCM(bitDepthCode='16') {
516
    this.fromScratch(
517
      this.fmt.numChannels,
518
      this.fmt.sampleRate,
519
      '16',
520
      imaadpcm.decode(this.data.samples, this.fmt.blockAlign),
521
      {container: this.correctContainer_()});
522
    if (bitDepthCode != '16') {
523
      this.toBitDepth(bitDepthCode);
524
    }
525
  }
526
527
  /**
528
   * Encode a 16-bit wave file as 8-bit A-Law.
529
   */
530
  toALaw() {
531
    this.assure16Bit_();
532
    /** @type {!Int16Array} */
533
    let output = new Int16Array(this.data.samples.length / 2);
534
    unpackArrayTo(this.data.samples, this.dataType, output);
535
    this.fromScratch(
536
      this.fmt.numChannels,
537
      this.fmt.sampleRate,
538
      '8a',
539
      alawmulaw.alaw.encode(output),
540
      {container: this.correctContainer_()});
541
  }
542
543
  /**
544
   * Decode a 8-bit A-Law wave file into a 16-bit wave file.
545
   * @param {string} bitDepthCode The new bit depth of the samples.
546
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
547
   *    Optional. Default is 16.
548
   */
549
  fromALaw(bitDepthCode='16') {
550
    this.fromScratch(
551
      this.fmt.numChannels,
552
      this.fmt.sampleRate,
553
      '16',
554
      alawmulaw.alaw.decode(this.data.samples),
555
      {container: this.correctContainer_()});
556
    if (bitDepthCode != '16') {
557
      this.toBitDepth(bitDepthCode);
558
    }
559
  }
560
561
  /**
562
   * Encode 16-bit wave file as 8-bit mu-Law.
563
   */
564
  toMuLaw() {
565
    this.assure16Bit_();
566
    /** @type {!Int16Array} */
567
    let output = new Int16Array(this.data.samples.length / 2);
568
    unpackArrayTo(this.data.samples, this.dataType, output);
569
    this.fromScratch(
570
      this.fmt.numChannels,
571
      this.fmt.sampleRate,
572
      '8m',
573
      alawmulaw.mulaw.encode(output),
574
      {container: this.correctContainer_()});
575
  }
576
577
  /**
578
   * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
579
   * @param {string} bitDepthCode The new bit depth of the samples.
580
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
581
   *    Optional. Default is 16.
582
   */
583
  fromMuLaw(bitDepthCode='16') {
584
    this.fromScratch(
585
      this.fmt.numChannels,
586
      this.fmt.sampleRate,
587
      '16',
588
      alawmulaw.mulaw.decode(this.data.samples),
589
      {container: this.correctContainer_()});
590
    if (bitDepthCode != '16') {
591
      this.toBitDepth(bitDepthCode);
592
    }
593
  }
594
595
  /**
596
   * Write a RIFF tag in the INFO chunk. If the tag do not exist,
597
   * then it is created. It if exists, it is overwritten.
598
   * @param {string} tag The tag name.
599
   * @param {string} value The tag value.
600
   * @throws {Error} If the tag name is not valid.
601
   */
602
  setTag(tag, value) {
603
    tag = this.fixTagName_(tag);
604
    /** @type {!Object} */
605
    let index = this.getTagIndex_(tag);
606
    if (index.TAG !== null) {
607
      this.LIST[index.LIST].subChunks[index.TAG].chunkSize =
608
        value.length + 1;
609
      this.LIST[index.LIST].subChunks[index.TAG].value = value;
610
    } else if (index.LIST !== null) {
611
      this.LIST[index.LIST].subChunks.push({
612
        chunkId: tag,
613
        chunkSize: value.length + 1,
614
        value: value});
615
    } else {
616
      this.LIST.push({
617
        chunkId: 'LIST',
618
        chunkSize: 8 + value.length + 1,
619
        format: 'INFO',
620
        subChunks: []});
621
      this.LIST[this.LIST.length - 1].subChunks.push({
622
        chunkId: tag,
623
        chunkSize: value.length + 1,
624
        value: value});
625
    }
626
  }
627
628
  /**
629
   * Return the value of a RIFF tag in the INFO chunk.
630
   * @param {string} tag The tag name.
631
   * @return {?string} The value if the tag is found, null otherwise.
632
   */
633
  getTag(tag) {
634
    /** @type {!Object} */
635
    let index = this.getTagIndex_(tag);
636
    if (index.TAG !== null) {
637
      return this.LIST[index.LIST].subChunks[index.TAG].value;
638
    }
639
    return null;
640
  }
641
642
  /**
643
   * Return a Object<tag, value> with the RIFF tags in the file.
644
   * @return {!Object<string, string>} The file tags.
645
   */
646
  listTags() {
647
    /** @type {?number} */
648
    let index = this.getLISTINFOIndex_();
649
    /** @type {!Object} */
650
    let tags = {};
651
    if (index !== null) {
652
      for (let i = 0, len = this.LIST[index].subChunks.length; i < len; i++) {
653
        tags[this.LIST[index].subChunks[i].chunkId] =
654
          this.LIST[index].subChunks[i].value;
655
      }
656
    }
657
    return tags;
658
  }
659
660
  /**
661
   * Remove a RIFF tag in the INFO chunk.
662
   * @param {string} tag The tag name.
663
   * @return {boolean} True if a tag was deleted.
664
   */
665
  deleteTag(tag) {
666
    /** @type {!Object} */
667
    let index = this.getTagIndex_(tag);
668
    if (index.TAG !== null) {
669
      this.LIST[index.LIST].subChunks.splice(index.TAG, 1);
670
      return true;
671
    }
672
    return false;
673
  }
674
675
  /**
676
   * Create a cue point in the wave file.
677
   * @param {number} position The cue point position in milliseconds.
678
   * @param {string} labl The LIST adtl labl text of the marker. Optional.
679
   */
680
  setCuePoint(position, labl='') {
681
    this.cue.chunkId = 'cue ';
682
    position = (position * this.fmt.sampleRate) / 1000;
683
    /** @type {!Array<!Object>} */
684
    let existingPoints = this.getCuePoints_();
685
    this.clearLISTadtl_();
686
    /** @type {number} */
687
    let len = this.cue.points.length;
688
    this.cue.points = [];
689
    /** @type {boolean} */
690
    let hasSet = false;
691
    if (len === 0) {
692
      this.setCuePoint_(position, 1, labl);
693
    } else {
694
      for (let i = 0; i < len; i++) {
695
        if (existingPoints[i].dwPosition > position && !hasSet) {
696
          this.setCuePoint_(position, i + 1, labl);
697
          this.setCuePoint_(
698
            existingPoints[i].dwPosition,
699
            i + 2,
700
            existingPoints[i].label);
701
          hasSet = true;
702
        } else {
703
          this.setCuePoint_(
704
            existingPoints[i].dwPosition,
705
            i + 1,
706
            existingPoints[i].label);
707
        }
708
      }
709
      if (!hasSet) {
710
        this.setCuePoint_(position, this.cue.points.length + 1, labl);
711
      }
712
    }
713
    this.cue.dwCuePoints = this.cue.points.length;
714
  }
715
716
  /**
717
   * Remove a cue point from a wave file.
718
   * @param {number} index the index of the point. First is 1,
719
   *    second is 2, and so on.
720
   */
721
  deleteCuePoint(index) {
722
    this.cue.chunkId = 'cue ';
723
    /** @type {!Array<!Object>} */
724
    let existingPoints = this.getCuePoints_();
725
    this.clearLISTadtl_();
726
    /** @type {number} */
727
    let len = this.cue.points.length;
728
    this.cue.points = [];
729
    for (let i = 0; i < len; i++) {
730
      if (i + 1 !== index) {
731
        this.setCuePoint_(
732
          existingPoints[i].dwPosition,
733
          i + 1,
734
          existingPoints[i].label);
735
      }
736
    }
737
    this.cue.dwCuePoints = this.cue.points.length;
738
    if (this.cue.dwCuePoints) {
739
      this.cue.chunkId = 'cue ';
740
    } else {
741
      this.cue.chunkId = '';
742
      this.clearLISTadtl_();
743
    }
744
  }
745
746
  /**
747
   * Return an array with all cue points in the file, in the order they appear
748
   * in the file.
749
   * The difference between this method and using the list in WaveFile.cue
750
   * is that the return value of this method includes the position in
751
   * milliseconds of each cue point (WaveFile.cue only have the sample offset)
752
   * @return {!Array<!Object>}
753
   */
754
  listCuePoints() {
755
    /** @type {!Array<!Object>} */
756
    let points = this.getCuePoints_();
757
    for (let i = 0, len = points.length; i < len; i++) {
758
      points[i].milliseconds =
759
        (points[i].dwPosition / this.fmt.sampleRate) * 1000;
760
    }
761
    return points;
762
  }
763
764
  /**
765
   * Update the label of a cue point.
766
   * @param {number} pointIndex The ID of the cue point.
767
   * @param {string} label The new text for the label.
768
   */
769
  updateLabel(pointIndex, label) {
770
    /** @type {?number} */
771
    let cIndex = this.getAdtlChunk_();
772
    if (cIndex !== null) {
773
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
774
        if (this.LIST[cIndex].subChunks[i].dwName ==
775
            pointIndex) {
776
          this.LIST[cIndex].subChunks[i].value = label;
777
        }
778
      }
779
    }
780
  }
781
782
  /**
783
   * Set the string code of the bit depth based on the 'fmt ' chunk.
784
   * @private
785
   */
786
  bitDepthFromFmt_() {
787
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
788
      this.bitDepth = '32f';
789
    } else if (this.fmt.audioFormat === 6) {
790
      this.bitDepth = '8a';
791
    } else if (this.fmt.audioFormat === 7) {
792
      this.bitDepth = '8m';
793
    } else {
794
      this.bitDepth = this.fmt.bitsPerSample.toString();
795
    }
796
  }
797
  
798
  /**
799
   * Push a new cue point in this.cue.points.
800
   * @param {number} position The position in milliseconds.
801
   * @param {number} dwName the dwName of the cue point
802
   * @private
803
   */
804
  setCuePoint_(position, dwName, label) {
805
    this.cue.points.push({
806
      dwName: dwName,
807
      dwPosition: position,
808
      fccChunk: 'data',
809
      dwChunkStart: 0,
810
      dwBlockStart: 0,
811
      dwSampleOffset: position,
812
    });
813
    this.setLabl_(dwName, label);
814
  }
815
816
  /**
817
   * Return an array with all cue points in the file, in the order they appear
818
   * in the file.
819
   * @return {!Array<!Object>}
820
   * @private
821
   */
822
  getCuePoints_() {
823
    /** @type {!Array<!Object>} */
824
    let points = [];
825
    for (let i = 0, len = this.cue.points.length; i < len; i++) {
826
      points.push({
827
        dwPosition: this.cue.points[i].dwPosition,
828
        label: this.getLabelForCuePoint_(
829
          this.cue.points[i].dwName)});
830
    }
831
    return points;
832
  }
833
834
  /**
835
   * Return the label of a cue point.
836
   * @param {number} pointDwName The ID of the cue point.
837
   * @return {string}
838
   * @private
839
   */
840
  getLabelForCuePoint_(pointDwName) {
841
    /** @type {?number} */
842
    let cIndex = this.getAdtlChunk_();
843
    if (cIndex !== null) {
844
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
845
        if (this.LIST[cIndex].subChunks[i].dwName ==
846
            pointDwName) {
847
          return this.LIST[cIndex].subChunks[i].value;
848
        }
849
      }
850
    }
851
    return '';
852
  }
853
854
  /**
855
   * Clear any LIST chunk labeled as 'adtl'.
856
   * @private
857
   */
858
  clearLISTadtl_() {
859
    for (let i = 0, len = this.LIST.length; i < len; i++) {
860
      if (this.LIST[i].format == 'adtl') {
861
        this.LIST.splice(i);
862
      }
863
    }
864
  }
865
866
  /**
867
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
868
   * @param {number} dwName The ID of the cue point.
869
   * @param {string} label The label for the cue point.
870
   * @private
871
   */
872
  setLabl_(dwName, label) {
873
    /** @type {?number} */
874
    let adtlIndex = this.getAdtlChunk_();
875
    if (adtlIndex === null) {
876
      this.LIST.push({
877
        chunkId: 'LIST',
878
        chunkSize: 4,
879
        format: 'adtl',
880
        subChunks: []});
881
      adtlIndex = this.LIST.length - 1;
882
    }
883
    this.setLabelText_(adtlIndex === null ? 0 : adtlIndex, dwName, label);
884
  }
885
886
  /**
887
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
888
   * @param {number} adtlIndex The index of the 'adtl' LIST in this.LIST.
889
   * @param {number} dwName The ID of the cue point.
890
   * @param {string} label The label for the cue point.
891
   * @private
892
   */
893
  setLabelText_(adtlIndex, dwName, label) {
894
    this.LIST[adtlIndex].subChunks.push({
895
      chunkId: 'labl',
896
      chunkSize: label.length,
897
      dwName: dwName,
898
      value: label
899
    });
900
    this.LIST[adtlIndex].chunkSize += label.length + 4 + 4 + 4 + 1;
901
  }
902
903
  /**
904
   * Return the index of the 'adtl' LIST in this.LIST.
905
   * @return {?number}
906
   * @private
907
   */
908
  getAdtlChunk_() {
909
    for (let i = 0, len = this.LIST.length; i < len; i++) {
910
      if (this.LIST[i].format == 'adtl') {
911
        return i;
912
      }
913
    }
914
    return null;
915
  }
916
917
  /**
918
   * Return the index of the INFO chunk in the LIST chunk.
919
   * @return {?number} the index of the INFO chunk.
920
   * @private
921
   */
922
  getLISTINFOIndex_() {
923
    /** @type {?number} */
924
    let index = null;
925
    for (let i = 0, len = this.LIST.length; i < len; i++) {
926
      if (this.LIST[i].format === 'INFO') {
927
        index = i;
928
        break;
929
      }
930
    }
931
    return index;
932
  }
933
934
  /**
935
   * Return the index of a tag in a FILE chunk.
936
   * @param {string} tag The tag name.
937
   * @return {!Object<string, ?number>}
938
   *    Object.LIST is the INFO index in LIST
939
   *    Object.TAG is the tag index in the INFO
940
   * @private
941
   */
942
  getTagIndex_(tag) {
943
    /** @type {!Object<string, ?number>} */
944
    let index = {LIST: null, TAG: null};
945
    for (let i = 0, len = this.LIST.length; i < len; i++) {
946
      if (this.LIST[i].format == 'INFO') {
947
        index.LIST = i;
948
        for (let j=0, subLen = this.LIST[i].subChunks.length; j < subLen; j++) {
949
          if (this.LIST[i].subChunks[j].chunkId == tag) {
950
            index.TAG = j;
951
            break;
952
          }
953
        }
954
        break;
955
      }
956
    }
957
    return index;
958
  }
959
960
  /**
961
   * Fix a RIFF tag format if possible, throw an error otherwise.
962
   * @param {string} tag The tag name.
963
   * @return {string} The tag name in proper fourCC format.
964
   * @private
965
   */
966
  fixTagName_(tag) {
967
    if (tag.constructor !== String) {
968
      throw new Error('Invalid tag name.');
969
    } else if (tag.length < 4) {
970
      for (let i = 0, len = 4 - tag.length; i < len; i++) {
971
        tag += ' ';
972
      }
973
    }
974
    return tag;
975
  }
976
977
  /**
978
   * Reset attributes that should emptied when a file is
979
   * created with the fromScratch() or fromBuffer() methods.
980
   * @private
981
   */
982
  clearHeader_() {
983
    this.fmt.cbSize = 0;
984
    this.fmt.validBitsPerSample = 0;
985
    this.fact.chunkId = '';
986
    this.ds64.chunkId = '';
987
  }
988
989
  /**
990
   * Make the file 16-bit if it is not.
991
   * @private
992
   */
993
  assure16Bit_() {
994
    this.assureUncompressed_();
995
    if (this.bitDepth != '16') {
996
      this.toBitDepth('16');
997
    }
998
  }
999
1000
  /**
1001
   * Uncompress the samples in case of a compressed file.
1002
   * @private
1003
   */
1004
  assureUncompressed_() {
1005
    if (this.bitDepth == '8a') {
1006
      this.fromALaw();
1007
    } else if (this.bitDepth == '8m') {
1008
      this.fromMuLaw();
1009
    } else if (this.bitDepth == '4') {
1010
      this.fromIMAADPCM();
1011
    }
1012
  }
1013
1014
  /**
1015
   * Update the type definition used to read and write the samples.
1016
   * @private
1017
   */
1018
  updateDataType_() {
1019
    this.dataType = {
1020
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
1021
      fp: this.bitDepth == '32f' || this.bitDepth == '64',
1022
      signed: this.bitDepth != '8',
1023
      be: this.container == 'RIFX'
1024
    };
1025
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
1026
      this.dataType.bits = 8;
1027
      this.dataType.signed = false;
1028
    }
1029
  }
1030
1031
  /**
1032
   * Return 'RIFF' if the container is 'RF64', the current container name
1033
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
1034
   * @return {string}
1035
   * @private
1036
   */
1037
  correctContainer_() {
1038
    return this.container == 'RF64' ? 'RIFF' : this.container;
1039
  }
1040
1041
  /**
1042
   * Return a .wav file byte buffer with the data from the WaveFile object.
1043
   * The return value of this method can be written straight to disk.
1044
   * @return {!Uint8Array} The wav file bytes.
1045
   * @private
1046
   */
1047
  writeWavBuffer() {
1048
    this.uInt16_.be = this.container === 'RIFX';
1049
    this.uInt32_.be = this.uInt16_.be;
1050
    /** @type {!Array<!Array<number>>} */
1051
    let fileBody = [
1052
      this.getJunkBytes_(),
1053
      this.getDs64Bytes_(),
1054
      this.getBextBytes_(),
1055
      this.getFmtBytes_(),
1056
      this.getFactBytes_(),
1057
      packString(this.data.chunkId),
1058
      pack(this.data.samples.length, this.uInt32_),
1059
      this.data.samples,
1060
      this.getCueBytes_(),
1061
      this.getSmplBytes_(),
1062
      this.getLISTBytes_()
1063
    ];
1064
    /** @type {number} */
1065
    let fileBodyLength = 0;
1066
    for (let i=0; i<fileBody.length; i++) {
1067
      fileBodyLength += fileBody[i].length;
1068
    }
1069
    /** @type {!Uint8Array} */
1070
    let file = new Uint8Array(fileBodyLength + 12);
1071
    /** @type {number} */
1072
    let index = 0;
1073
    index = packStringTo(this.container, file, index);
1074
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
1075
    index = packStringTo(this.format, file, index);
1076
    for (let i=0; i<fileBody.length; i++) {
1077
      file.set(fileBody[i], index);
1078
      index += fileBody[i].length;
1079
    }
1080
    return file;
1081
  }
1082
1083
  /**
1084
   * Return the bytes of the 'bext' chunk.
1085
   * @private
1086
   */
1087
  getBextBytes_() {
1088
    /** @type {!Array<number>} */
1089
    let bytes = [];
1090
    this.enforceBext_();
1091
    if (this.bext.chunkId) {
1092
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
1093
      bytes = bytes.concat(
1094
        packString(this.bext.chunkId),
1095
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
1096
        writeString(this.bext.description, 256),
1097
        writeString(this.bext.originator, 32),
1098
        writeString(this.bext.originatorReference, 32),
1099
        writeString(this.bext.originationDate, 10),
1100
        writeString(this.bext.originationTime, 8),
1101
        pack(this.bext.timeReference[0], this.uInt32_),
1102
        pack(this.bext.timeReference[1], this.uInt32_),
1103
        pack(this.bext.version, this.uInt16_),
1104
        writeString(this.bext.UMID, 64),
1105
        pack(this.bext.loudnessValue, this.uInt16_),
1106
        pack(this.bext.loudnessRange, this.uInt16_),
1107
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
1108
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
1109
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
1110
        writeString(this.bext.reserved, 180),
1111
        writeString(
1112
          this.bext.codingHistory, this.bext.codingHistory.length));
1113
    }
1114
    return bytes;
1115
  }
1116
1117
  /**
1118
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
1119
   * @private
1120
   */
1121
  enforceBext_() {
1122
    for (let prop in this.bext) {
1123
      if (this.bext.hasOwnProperty(prop)) {
1124
        if (this.bext[prop] && prop != 'timeReference') {
1125
          this.bext.chunkId = 'bext';
1126
          break;
1127
        }
1128
      }
1129
    }
1130
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
1131
      this.bext.chunkId = 'bext';
1132
    }
1133
  }
1134
1135
  /**
1136
   * Return the bytes of the 'ds64' chunk.
1137
   * @return {!Array<number>} The 'ds64' chunk bytes.
1138
   * @private
1139
   */
1140
  getDs64Bytes_() {
1141
    /** @type {!Array<number>} */
1142
    let bytes = [];
1143
    if (this.ds64.chunkId) {
1144
      bytes = bytes.concat(
1145
        packString(this.ds64.chunkId),
1146
        pack(this.ds64.chunkSize, this.uInt32_),
1147
        pack(this.ds64.riffSizeHigh, this.uInt32_),
1148
        pack(this.ds64.riffSizeLow, this.uInt32_),
1149
        pack(this.ds64.dataSizeHigh, this.uInt32_),
1150
        pack(this.ds64.dataSizeLow, this.uInt32_),
1151
        pack(this.ds64.originationTime, this.uInt32_),
1152
        pack(this.ds64.sampleCountHigh, this.uInt32_),
1153
        pack(this.ds64.sampleCountLow, this.uInt32_));
1154
    }
1155
    //if (this.ds64.tableLength) {
1156
    //  ds64Bytes = ds64Bytes.concat(
1157
    //    pack(this.ds64.tableLength, this.uInt32_),
1158
    //    this.ds64.table);
1159
    //}
1160
    return bytes;
1161
  }
1162
1163
  /**
1164
   * Return the bytes of the 'cue ' chunk.
1165
   * @return {!Array<number>} The 'cue ' chunk bytes.
1166
   * @private
1167
   */
1168
  getCueBytes_() {
1169
    /** @type {!Array<number>} */
1170
    let bytes = [];
1171
    if (this.cue.chunkId) {
1172
      /** @type {!Array<number>} */
1173
      let cuePointsBytes = this.getCuePointsBytes_();
1174
      bytes = bytes.concat(
1175
        packString(this.cue.chunkId),
1176
        pack(cuePointsBytes.length + 4, this.uInt32_),
1177
        pack(this.cue.dwCuePoints, this.uInt32_),
1178
        cuePointsBytes);
1179
    }
1180
    return bytes;
1181
  }
1182
1183
  /**
1184
   * Return the bytes of the 'cue ' points.
1185
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
1186
   * @private
1187
   */
1188
  getCuePointsBytes_() {
1189
    /** @type {!Array<number>} */
1190
    let points = [];
1191
    for (let i=0; i<this.cue.dwCuePoints; i++) {
1192
      points = points.concat(
1193
        pack(this.cue.points[i].dwName, this.uInt32_),
1194
        pack(this.cue.points[i].dwPosition, this.uInt32_),
1195
        packString(this.cue.points[i].fccChunk),
1196
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
1197
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
1198
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
1199
    }
1200
    return points;
1201
  }
1202
1203
  /**
1204
   * Return the bytes of the 'smpl' chunk.
1205
   * @return {!Array<number>} The 'smpl' chunk bytes.
1206
   * @private
1207
   */
1208
  getSmplBytes_() {
1209
    /** @type {!Array<number>} */
1210
    let bytes = [];
1211
    if (this.smpl.chunkId) {
1212
      /** @type {!Array<number>} */
1213
      let smplLoopsBytes = this.getSmplLoopsBytes_();
1214
      bytes = bytes.concat(
1215
        packString(this.smpl.chunkId),
1216
        pack(smplLoopsBytes.length + 36, this.uInt32_),
1217
        pack(this.smpl.dwManufacturer, this.uInt32_),
1218
        pack(this.smpl.dwProduct, this.uInt32_),
1219
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
1220
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
1221
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
1222
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
1223
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
1224
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
1225
        pack(this.smpl.dwSamplerData, this.uInt32_),
1226
        smplLoopsBytes);
1227
    }
1228
    return bytes;
1229
  }
1230
1231
  /**
1232
   * Return the bytes of the 'smpl' loops.
1233
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
1234
   * @private
1235
   */
1236
  getSmplLoopsBytes_() {
1237
    /** @type {!Array<number>} */
1238
    let loops = [];
1239
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
1240
      loops = loops.concat(
1241
        pack(this.smpl.loops[i].dwName, this.uInt32_),
1242
        pack(this.smpl.loops[i].dwType, this.uInt32_),
1243
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
1244
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
1245
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
1246
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
1247
    }
1248
    return loops;
1249
  }
1250
1251
  /**
1252
   * Return the bytes of the 'fact' chunk.
1253
   * @return {!Array<number>} The 'fact' chunk bytes.
1254
   * @private
1255
   */
1256
  getFactBytes_() {
1257
    /** @type {!Array<number>} */
1258
    let bytes = [];
1259
    if (this.fact.chunkId) {
1260
      bytes = bytes.concat(
1261
        packString(this.fact.chunkId),
1262
        pack(this.fact.chunkSize, this.uInt32_),
1263
        pack(this.fact.dwSampleLength, this.uInt32_));
1264
    }
1265
    return bytes;
1266
  }
1267
1268
  /**
1269
   * Return the bytes of the 'fmt ' chunk.
1270
   * @return {!Array<number>} The 'fmt' chunk bytes.
1271
   * @throws {Error} if no 'fmt ' chunk is present.
1272
   * @private
1273
   */
1274
  getFmtBytes_() {
1275
    /** @type {!Array<number>} */
1276
    let fmtBytes = [];
1277
    if (this.fmt.chunkId) {
1278
      return fmtBytes.concat(
1279
        packString(this.fmt.chunkId),
1280
        pack(this.fmt.chunkSize, this.uInt32_),
1281
        pack(this.fmt.audioFormat, this.uInt16_),
1282
        pack(this.fmt.numChannels, this.uInt16_),
1283
        pack(this.fmt.sampleRate, this.uInt32_),
1284
        pack(this.fmt.byteRate, this.uInt32_),
1285
        pack(this.fmt.blockAlign, this.uInt16_),
1286
        pack(this.fmt.bitsPerSample, this.uInt16_),
1287
        this.getFmtExtensionBytes_());
1288
    }
1289
    throw Error('Could not find the "fmt " chunk');
1290
  }
1291
1292
  /**
1293
   * Return the bytes of the fmt extension fields.
1294
   * @return {!Array<number>} The fmt extension bytes.
1295
   * @private
1296
   */
1297
  getFmtExtensionBytes_() {
1298
    /** @type {!Array<number>} */
1299
    let extension = [];
1300
    if (this.fmt.chunkSize > 16) {
1301
      extension = extension.concat(
1302
        pack(this.fmt.cbSize, this.uInt16_));
1303
    }
1304
    if (this.fmt.chunkSize > 18) {
1305
      extension = extension.concat(
1306
        pack(this.fmt.validBitsPerSample, this.uInt16_));
1307
    }
1308
    if (this.fmt.chunkSize > 20) {
1309
      extension = extension.concat(
1310
        pack(this.fmt.dwChannelMask, this.uInt32_));
1311
    }
1312
    if (this.fmt.chunkSize > 24) {
1313
      extension = extension.concat(
1314
        pack(this.fmt.subformat[0], this.uInt32_),
1315
        pack(this.fmt.subformat[1], this.uInt32_),
1316
        pack(this.fmt.subformat[2], this.uInt32_),
1317
        pack(this.fmt.subformat[3], this.uInt32_));
1318
    }
1319
    return extension;
1320
  }
1321
1322
  /**
1323
   * Return the bytes of the 'LIST' chunk.
1324
   * @return {!Array<number>} The 'LIST' chunk bytes.
1325
   */
1326
  getLISTBytes_() {
1327
    /** @type {!Array<number>} */
1328
    let bytes = [];
1329
    for (let i=0; i<this.LIST.length; i++) {
1330
      /** @type {!Array<number>} */
1331
      let subChunksBytes = this.getLISTSubChunksBytes_(
1332
          this.LIST[i].subChunks, this.LIST[i].format);
1333
      bytes = bytes.concat(
1334
        packString(this.LIST[i].chunkId),
1335
        pack(subChunksBytes.length + 4, this.uInt32_),
1336
        packString(this.LIST[i].format),
1337
        subChunksBytes);
1338
    }
1339
    return bytes;
1340
  }
1341
1342
  /**
1343
   * Return the bytes of the sub chunks of a 'LIST' chunk.
1344
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
1345
   * @param {string} format The format of the 'LIST' chunk.
1346
   *    Currently supported values are 'adtl' or 'INFO'.
1347
   * @return {!Array<number>} The sub chunk bytes.
1348
   * @private
1349
   */
1350
  getLISTSubChunksBytes_(subChunks, format) {
1351
    /** @type {!Array<number>} */
1352
    let bytes = [];
1353
    for (let i=0; i<subChunks.length; i++) {
1354
      if (format == 'INFO') {
1355
        bytes = bytes.concat(
1356
          packString(subChunks[i].chunkId),
1357
          pack(subChunks[i].value.length + 1, this.uInt32_),
1358
          writeString(
1359
            subChunks[i].value, subChunks[i].value.length));
1360
        bytes.push(0);
1361
      } else if (format == 'adtl') {
1362
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
1363
          bytes = bytes.concat(
1364
            packString(subChunks[i].chunkId),
1365
            pack(
1366
              subChunks[i].value.length + 4 + 1, this.uInt32_),
1367
            pack(subChunks[i].dwName, this.uInt32_),
1368
            writeString(
1369
              subChunks[i].value,
1370
              subChunks[i].value.length));
1371
          bytes.push(0);
1372
        } else if (subChunks[i].chunkId == 'ltxt') {
1373
          bytes = bytes.concat(
1374
            this.getLtxtChunkBytes_(subChunks[i]));
1375
        }
1376
      }
1377
      if (bytes.length % 2) {
1378
        bytes.push(0);
1379
      }
1380
    }
1381
    return bytes;
1382
  }
1383
1384
  /**
1385
   * Return the bytes of a 'ltxt' chunk.
1386
   * @param {!Object} ltxt the 'ltxt' chunk.
1387
   * @private
1388
   */
1389
  getLtxtChunkBytes_(ltxt) {
1390
    return [].concat(
1391
      packString(ltxt.chunkId),
1392
      pack(ltxt.value.length + 20, this.uInt32_),
1393
      pack(ltxt.dwName, this.uInt32_),
1394
      pack(ltxt.dwSampleLength, this.uInt32_),
1395
      pack(ltxt.dwPurposeID, this.uInt32_),
1396
      pack(ltxt.dwCountry, this.uInt16_),
1397
      pack(ltxt.dwLanguage, this.uInt16_),
1398
      pack(ltxt.dwDialect, this.uInt16_),
1399
      pack(ltxt.dwCodePage, this.uInt16_),
1400
      writeString(ltxt.value, ltxt.value.length));
1401
  }
1402
1403
  /**
1404
   * Return the bytes of the 'junk' chunk.
1405
   * @private
1406
   */
1407
  getJunkBytes_() {
1408
    /** @type {!Array<number>} */
1409
    let bytes = [];
1410
    if (this.junk.chunkId) {
1411
      return bytes.concat(
1412
        packString(this.junk.chunkId),
1413
        pack(this.junk.chunkData.length, this.uInt32_),
1414
        this.junk.chunkData);
1415
    }
1416
    return bytes;
1417
  }
1418
1419
  /**
1420
   * Set up the WaveFile object from a byte buffer.
1421
   * @param {!Uint8Array} wavBuffer The buffer.
1422
   * @param {boolean} samples True if the samples should be loaded.
1423
   * @throws {Error} If container is not RIFF, RIFX or RF64.
1424
   * @throws {Error} If format is not WAVE.
1425
   * @throws {Error} If no 'fmt ' chunk is found.
1426
   * @throws {Error} If no 'data' chunk is found.
1427
   */
1428
  readWavBuffer(wavBuffer, samples) {
1429
    this.head_ = 0;
1430
    this.readRIFFChunk_(wavBuffer);
1431
    if (this.format != 'WAVE') {
1432
      throw Error('Could not find the "WAVE" format identifier');
1433
    }
1434
    this.getSignature_(wavBuffer);
1435
    this.readDs64Chunk_(wavBuffer);
1436
    this.readFmtChunk_(wavBuffer);
1437
    this.readFactChunk_(wavBuffer);
1438
    this.readBextChunk_(wavBuffer);
1439
    this.readCueChunk_(wavBuffer);
1440
    this.readSmplChunk_(wavBuffer);
1441
    this.readDataChunk_(wavBuffer, samples);
1442
    this.readJunkChunk_(wavBuffer);
1443
    this.readLISTChunk_(wavBuffer);
1444
  }
1445
1446
  /**
1447
   * Read the 'fmt ' chunk of a wave file.
1448
   * @param {!Uint8Array} buffer The wav file buffer.
1449
   * @throws {Error} If no 'fmt ' chunk is found.
1450
   * @private
1451
   */
1452
  readFmtChunk_(buffer) {
1453
    /** @type {?Object} */
1454
    let chunk = this.findChunk_('fmt ');
1455
    if (chunk) {
1456
      this.head_ = chunk.chunkData.start;
1457
      this.fmt.chunkId = chunk.chunkId;
1458
      this.fmt.chunkSize = chunk.chunkSize;
1459
      this.fmt.audioFormat = this.read_(buffer, this.uInt16_);
1460
      this.fmt.numChannels = this.read_(buffer, this.uInt16_);
1461
      this.fmt.sampleRate = this.read_(buffer, this.uInt32_);
1462
      this.fmt.byteRate = this.read_(buffer, this.uInt32_);
1463
      this.fmt.blockAlign = this.read_(buffer, this.uInt16_);
1464
      this.fmt.bitsPerSample = this.read_(buffer, this.uInt16_);
1465
      this.readFmtExtension_(buffer);
1466
    } else {
1467
      throw Error('Could not find the "fmt " chunk');
1468
    }
1469
  }
1470
1471
  /**
1472
   * Read the 'fmt ' chunk extension.
1473
   * @param {!Uint8Array} buffer The wav file buffer.
1474
   * @private
1475
   */
1476
  readFmtExtension_(buffer) {
1477
    if (this.fmt.chunkSize > 16) {
1478
      this.fmt.cbSize = this.read_(buffer, this.uInt16_);
1479
      if (this.fmt.chunkSize > 18) {
1480
        this.fmt.validBitsPerSample = this.read_(buffer, this.uInt16_);
1481
        if (this.fmt.chunkSize > 20) {
1482
          this.fmt.dwChannelMask = this.read_(buffer, this.uInt32_);
1483
          this.fmt.subformat = [
1484
            this.read_(buffer, this.uInt32_),
1485
            this.read_(buffer, this.uInt32_),
1486
            this.read_(buffer, this.uInt32_),
1487
            this.read_(buffer, this.uInt32_)];
1488
        }
1489
      }
1490
    }
1491
  }
1492
1493
  /**
1494
   * Read the 'fact' chunk of a wav file.
1495
   * @param {!Uint8Array} buffer The wav file buffer.
1496
   * @private
1497
   */
1498
  readFactChunk_(buffer) {
1499
    /** @type {?Object} */
1500
    let chunk = this.findChunk_('fact');
1501
    if (chunk) {
1502
      this.head_ = chunk.chunkData.start;
1503
      this.fact.chunkId = chunk.chunkId;
1504
      this.fact.chunkSize = chunk.chunkSize;
1505
      this.fact.dwSampleLength = this.read_(buffer, this.uInt32_);
1506
    }
1507
  }
1508
1509
  /**
1510
   * Read the 'cue ' chunk of a wave file.
1511
   * @param {!Uint8Array} buffer The wav file buffer.
1512
   * @private
1513
   */
1514
  readCueChunk_(buffer) {
1515
    /** @type {?Object} */
1516
    let chunk = this.findChunk_('cue ');
1517
    if (chunk) {
1518
      this.head_ = chunk.chunkData.start;
1519
      this.cue.chunkId = chunk.chunkId;
1520
      this.cue.chunkSize = chunk.chunkSize;
1521
      this.cue.dwCuePoints = this.read_(buffer, this.uInt32_);
1522
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
1523
        this.cue.points.push({
1524
          dwName: this.read_(buffer, this.uInt32_),
1525
          dwPosition: this.read_(buffer, this.uInt32_),
1526
          fccChunk: this.readString_(buffer, 4),
1527
          dwChunkStart: this.read_(buffer, this.uInt32_),
1528
          dwBlockStart: this.read_(buffer, this.uInt32_),
1529
          dwSampleOffset: this.read_(buffer, this.uInt32_),
1530
        });
1531
      }
1532
    }
1533
  }
1534
1535
  /**
1536
   * Read the 'smpl' chunk of a wave file.
1537
   * @param {!Uint8Array} buffer The wav file buffer.
1538
   * @private
1539
   */
1540
  readSmplChunk_(buffer) {
1541
    /** @type {?Object} */
1542
    let chunk = this.findChunk_('smpl');
1543
    if (chunk) {
1544
      this.head_ = chunk.chunkData.start;
1545
      this.smpl.chunkId = chunk.chunkId;
1546
      this.smpl.chunkSize = chunk.chunkSize;
1547
      this.smpl.dwManufacturer = this.read_(buffer, this.uInt32_);
1548
      this.smpl.dwProduct = this.read_(buffer, this.uInt32_);
1549
      this.smpl.dwSamplePeriod = this.read_(buffer, this.uInt32_);
1550
      this.smpl.dwMIDIUnityNote = this.read_(buffer, this.uInt32_);
1551
      this.smpl.dwMIDIPitchFraction = this.read_(buffer, this.uInt32_);
1552
      this.smpl.dwSMPTEFormat = this.read_(buffer, this.uInt32_);
1553
      this.smpl.dwSMPTEOffset = this.read_(buffer, this.uInt32_);
1554
      this.smpl.dwNumSampleLoops = this.read_(buffer, this.uInt32_);
1555
      this.smpl.dwSamplerData = this.read_(buffer, this.uInt32_);
1556
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
1557
        this.smpl.loops.push({
1558
          dwName: this.read_(buffer, this.uInt32_),
1559
          dwType: this.read_(buffer, this.uInt32_),
1560
          dwStart: this.read_(buffer, this.uInt32_),
1561
          dwEnd: this.read_(buffer, this.uInt32_),
1562
          dwFraction: this.read_(buffer, this.uInt32_),
1563
          dwPlayCount: this.read_(buffer, this.uInt32_),
1564
        });
1565
      }
1566
    }
1567
  }
1568
1569
  /**
1570
   * Read the 'data' chunk of a wave file.
1571
   * @param {!Uint8Array} buffer The wav file buffer.
1572
   * @param {boolean} samples True if the samples should be loaded.
1573
   * @throws {Error} If no 'data' chunk is found.
1574
   * @private
1575
   */
1576
  readDataChunk_(buffer, samples) {
1577
    /** @type {?Object} */
1578
    let chunk = this.findChunk_('data');
1579
    if (chunk) {
1580
      this.data.chunkId = 'data';
1581
      this.data.chunkSize = chunk.chunkSize;
1582
      if (samples) {
1583
        this.data.samples = buffer.slice(
1584
          chunk.chunkData.start,
1585
          chunk.chunkData.end);
1586
      }
1587
    } else {
1588
      throw Error('Could not find the "data" chunk');
1589
    }
1590
  }
1591
1592
  /**
1593
   * Read the 'bext' chunk of a wav file.
1594
   * @param {!Uint8Array} buffer The wav file buffer.
1595
   * @private
1596
   */
1597
  readBextChunk_(buffer) {
1598
    /** @type {?Object} */
1599
    let chunk = this.findChunk_('bext');
1600
    if (chunk) {
1601
      this.head_ = chunk.chunkData.start;
1602
      this.bext.chunkId = chunk.chunkId;
1603
      this.bext.chunkSize = chunk.chunkSize;
1604
      this.bext.description = this.readString_(buffer, 256);
1605
      this.bext.originator = this.readString_(buffer, 32);
1606
      this.bext.originatorReference = this.readString_(buffer, 32);
1607
      this.bext.originationDate = this.readString_(buffer, 10);
1608
      this.bext.originationTime = this.readString_(buffer, 8);
1609
      this.bext.timeReference = [
1610
        this.read_(buffer, this.uInt32_),
1611
        this.read_(buffer, this.uInt32_)];
1612
      this.bext.version = this.read_(buffer, this.uInt16_);
1613
      this.bext.UMID = this.readString_(buffer, 64);
1614
      this.bext.loudnessValue = this.read_(buffer, this.uInt16_);
1615
      this.bext.loudnessRange = this.read_(buffer, this.uInt16_);
1616
      this.bext.maxTruePeakLevel = this.read_(buffer, this.uInt16_);
1617
      this.bext.maxMomentaryLoudness = this.read_(buffer, this.uInt16_);
1618
      this.bext.maxShortTermLoudness = this.read_(buffer, this.uInt16_);
1619
      this.bext.reserved = this.readString_(buffer, 180);
1620
      this.bext.codingHistory = this.readString_(
1621
        buffer, this.bext.chunkSize - 602);
1622
    }
1623
  }
1624
1625
  /**
1626
   * Read the 'ds64' chunk of a wave file.
1627
   * @param {!Uint8Array} buffer The wav file buffer.
1628
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
1629
   * @private
1630
   */
1631
  readDs64Chunk_(buffer) {
1632
    /** @type {?Object} */
1633
    let chunk = this.findChunk_('ds64');
1634
    if (chunk) {
1635
      this.head_ = chunk.chunkData.start;
1636
      this.ds64.chunkId = chunk.chunkId;
1637
      this.ds64.chunkSize = chunk.chunkSize;
1638
      this.ds64.riffSizeHigh = this.read_(buffer, this.uInt32_);
1639
      this.ds64.riffSizeLow = this.read_(buffer, this.uInt32_);
1640
      this.ds64.dataSizeHigh = this.read_(buffer, this.uInt32_);
1641
      this.ds64.dataSizeLow = this.read_(buffer, this.uInt32_);
1642
      this.ds64.originationTime = this.read_(buffer, this.uInt32_);
1643
      this.ds64.sampleCountHigh = this.read_(buffer, this.uInt32_);
1644
      this.ds64.sampleCountLow = this.read_(buffer, this.uInt32_);
1645
      //if (wav.ds64.chunkSize > 28) {
1646
      //  wav.ds64.tableLength = unpack(
1647
      //    chunkData.slice(28, 32), uInt32_);
1648
      //  wav.ds64.table = chunkData.slice(
1649
      //     32, 32 + wav.ds64.tableLength);
1650
      //}
1651
    } else {
1652
      if (this.container == 'RF64') {
1653
        throw Error('Could not find the "ds64" chunk');
1654
      }
1655
    }
1656
  }
1657
1658
  /**
1659
   * Read the 'LIST' chunks of a wave file.
1660
   * @param {!Uint8Array} buffer The wav file buffer.
1661
   * @private
1662
   */
1663
  readLISTChunk_(buffer) {
1664
    /** @type {?Object} */
1665
    let listChunks = this.findChunk_('LIST', true);
1666
    if (listChunks !== null) {
1667
      for (let j=0; j < listChunks.length; j++) {
1668
        /** @type {!Object} */
1669
        let subChunk = listChunks[j];
1670
        this.LIST.push({
1671
          chunkId: subChunk.chunkId,
1672
          chunkSize: subChunk.chunkSize,
1673
          format: subChunk.format,
1674
          subChunks: []});
1675
        for (let x=0; x<subChunk.subChunks.length; x++) {
1676
          this.readLISTSubChunks_(subChunk.subChunks[x],
1677
            subChunk.format, buffer);
1678
        }
1679
      }
1680
    }
1681
  }
1682
1683
  /**
1684
   * Read the sub chunks of a 'LIST' chunk.
1685
   * @param {!Object} subChunk The 'LIST' subchunks.
1686
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
1687
   * @param {!Uint8Array} buffer The wav file buffer.
1688
   * @private
1689
   */
1690
  readLISTSubChunks_(subChunk, format, buffer) {
1691
    if (format == 'adtl') {
1692
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
1693
        this.head_ = subChunk.chunkData.start;
1694
        /** @type {!Object<string, string|number>} */
1695
        let item = {
1696
          chunkId: subChunk.chunkId,
1697
          chunkSize: subChunk.chunkSize,
1698
          dwName: this.read_(buffer, this.uInt32_)
1699
        };
1700
        if (subChunk.chunkId == 'ltxt') {
1701
          item.dwSampleLength = this.read_(buffer, this.uInt32_);
1702
          item.dwPurposeID = this.read_(buffer, this.uInt32_);
1703
          item.dwCountry = this.read_(buffer, this.uInt16_);
1704
          item.dwLanguage = this.read_(buffer, this.uInt16_);
1705
          item.dwDialect = this.read_(buffer, this.uInt16_);
1706
          item.dwCodePage = this.read_(buffer, this.uInt16_);
1707
        }
1708
        item.value = this.readZSTR_(buffer, this.head_);
1709
        this.LIST[this.LIST.length - 1].subChunks.push(item);
1710
      }
1711
    // RIFF INFO tags like ICRD, ISFT, ICMT
1712
    } else if(format == 'INFO') {
1713
      this.head_ = subChunk.chunkData.start;
1714
      this.LIST[this.LIST.length - 1].subChunks.push({
1715
        chunkId: subChunk.chunkId,
1716
        chunkSize: subChunk.chunkSize,
1717
        value: this.readZSTR_(buffer, this.head_)
1718
      });
1719
    }
1720
  }
1721
1722
  /**
1723
   * Read the 'junk' chunk of a wave file.
1724
   * @param {!Uint8Array} buffer The wav file buffer.
1725
   * @private
1726
   */
1727
  readJunkChunk_(buffer) {
1728
    /** @type {?Object} */
1729
    let chunk = this.findChunk_('junk');
1730
    if (chunk) {
1731
      this.junk = {
1732
        chunkId: chunk.chunkId,
1733
        chunkSize: chunk.chunkSize,
1734
        chunkData: [].slice.call(buffer.slice(
1735
          chunk.chunkData.start,
1736
          chunk.chunkData.end))
1737
      };
1738
    }
1739
  }
1740
1741
  /**
1742
   * Define the header of a wav file.
1743
   * @param {string} bitDepthCode The audio bit depth
1744
   * @param {number} numChannels The number of channels
1745
   * @param {number} sampleRate The sample rate.
1746
   * @param {number} numBytes The number of bytes each sample use.
1747
   * @param {number} samplesLength The length of the samples in bytes.
1748
   * @param {!Object} options The extra options, like container defintion.
1749
   * @private
1750
   */
1751
  makeWavHeader(
1752
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1753
    if (bitDepthCode == '4') {
1754
      this.createADPCMHeader_(
1755
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1756
1757
    } else if (bitDepthCode == '8a' || bitDepthCode == '8m') {
1758
      this.createALawMulawHeader_(
1759
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1760
1761
    } else if(Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
1762
        numChannels > 2) {
1763
      this.createExtensibleHeader_(
1764
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1765
1766
    } else {
1767
      this.createPCMHeader_(
1768
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);      
1769
    }
1770
  }
1771
1772
  /**
1773
   * Create the header of a linear PCM wave file.
1774
   * @param {string} bitDepthCode The audio bit depth
1775
   * @param {number} numChannels The number of channels
1776
   * @param {number} sampleRate The sample rate.
1777
   * @param {number} numBytes The number of bytes each sample use.
1778
   * @param {number} samplesLength The length of the samples in bytes.
1779
   * @param {!Object} options The extra options, like container defintion.
1780
   * @private
1781
   */
1782
  createPCMHeader_(
1783
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1784
    this.container = options.container;
1785
    this.chunkSize = 36 + samplesLength;
1786
    this.format = 'WAVE';
1787
    this.bitDepth = bitDepthCode;
1788
    this.fmt = {
1789
      chunkId: 'fmt ',
1790
      chunkSize: 16,
1791
      audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534,
1792
      numChannels: numChannels,
1793
      sampleRate: sampleRate,
1794
      byteRate: (numChannels * numBytes) * sampleRate,
1795
      blockAlign: numChannels * numBytes,
1796
      bitsPerSample: parseInt(bitDepthCode, 10),
1797
      cbSize: 0,
1798
      validBitsPerSample: 0,
1799
      dwChannelMask: 0,
1800
      subformat: []
1801
    };
1802
  }
1803
1804
  /**
1805
   * Create the header of a ADPCM wave file.
1806
   * @param {string} bitDepthCode The audio bit depth
1807
   * @param {number} numChannels The number of channels
1808
   * @param {number} sampleRate The sample rate.
1809
   * @param {number} numBytes The number of bytes each sample use.
1810
   * @param {number} samplesLength The length of the samples in bytes.
1811
   * @param {!Object} options The extra options, like container defintion.
1812
   * @private
1813
   */
1814
  createADPCMHeader_(
1815
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1816
    this.createPCMHeader_(
1817
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1818
    this.chunkSize = 40 + samplesLength;
1819
    this.fmt.chunkSize = 20;
1820
    this.fmt.byteRate = 4055;
1821
    this.fmt.blockAlign = 256;
1822
    this.fmt.bitsPerSample = 4;
1823
    this.fmt.cbSize = 2;
1824
    this.fmt.validBitsPerSample = 505;
1825
    this.fact = {
1826
      chunkId: 'fact',
1827
      chunkSize: 4,
1828
      dwSampleLength: samplesLength * 2
1829
    };
1830
  }
1831
1832
  /**
1833
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
1834
   * @param {string} bitDepthCode The audio bit depth
1835
   * @param {number} numChannels The number of channels
1836
   * @param {number} sampleRate The sample rate.
1837
   * @param {number} numBytes The number of bytes each sample use.
1838
   * @param {number} samplesLength The length of the samples in bytes.
1839
   * @param {!Object} options The extra options, like container defintion.
1840
   * @private
1841
   */
1842
  createExtensibleHeader_(
1843
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1844
    this.createPCMHeader_(
1845
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1846
    this.chunkSize = 36 + 24 + samplesLength;
1847
    this.fmt.chunkSize = 40;
1848
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
1849
    this.fmt.cbSize = 22;
1850
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
1851
    this.fmt.dwChannelMask = dwChannelMask(numChannels);
1852
    // subformat 128-bit GUID as 4 32-bit values
1853
    // only supports uncompressed integer PCM samples
1854
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
1855
  }
1856
1857
  /**
1858
   * Create the header of mu-Law and A-Law wave files.
1859
   * @param {string} bitDepthCode The audio bit depth
1860
   * @param {number} numChannels The number of channels
1861
   * @param {number} sampleRate The sample rate.
1862
   * @param {number} numBytes The number of bytes each sample use.
1863
   * @param {number} samplesLength The length of the samples in bytes.
1864
   * @param {!Object} options The extra options, like container defintion.
1865
   * @private
1866
   */
1867
  createALawMulawHeader_(
1868
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1869
    this.createPCMHeader_(
1870
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1871
    this.chunkSize = 40 + samplesLength;
1872
    this.fmt.chunkSize = 20;
1873
    this.fmt.cbSize = 2;
1874
    this.fmt.validBitsPerSample = 8;
1875
    this.fact = {
1876
      chunkId: 'fact',
1877
      chunkSize: 4,
1878
      dwSampleLength: samplesLength
1879
    };
1880
  }
1881
1882
  /**
1883
   * Validate the header of the file.
1884
   * @throws {Error} If any property of the object appears invalid.
1885
   * @private
1886
   */
1887
  validateWavHeader_() {
1888
    this.validateBitDepth_();
1889
    this.validateNumChannels_();
1890
    this.validateSampleRate_();
1891
  }
1892
1893
  /**
1894
   * Validate the bit depth.
1895
   * @return {boolean} True is the bit depth is valid.
1896
   * @throws {Error} If bit depth is invalid.
1897
   * @private
1898
   */
1899
  validateBitDepth_() {
1900
    if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) {
1901
      if (parseInt(this.bitDepth, 10) > 8 &&
1902
          parseInt(this.bitDepth, 10) < 54) {
1903
        return true;
1904
      }
1905
      throw new Error('Invalid bit depth.');
1906
    }
1907
    return true;
1908
  }
1909
1910
  /**
1911
   * Validate the number of channels.
1912
   * @return {boolean} True is the number of channels is valid.
1913
   * @throws {Error} If the number of channels is invalid.
1914
   * @private
1915
   */
1916
  validateNumChannels_() {
1917
    /** @type {number} */
1918
    let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
1919
    if (this.fmt.numChannels < 1 || blockAlign > 65535) {
1920
      throw new Error('Invalid number of channels.');
1921
    }
1922
    return true;
1923
  }
1924
1925
  /**
1926
   * Validate the sample rate value.
1927
   * @return {boolean} True is the sample rate is valid.
1928
   * @throws {Error} If the sample rate is invalid.
1929
   * @private
1930
   */
1931
  validateSampleRate_() {
1932
    /** @type {number} */
1933
    let byteRate = this.fmt.numChannels *
1934
      (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
1935
    if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
1936
      throw new Error('Invalid sample rate.');
1937
    }
1938
    return true;
1939
  }
1940
}
1941